package com.tsd.shiro.filter;

import com.tsd.core.cache.CacheOperatorApi;
import com.tsd.core.constants.Constants;
import com.tsd.core.datasource.DataSourceContextHolder;
import com.tsd.core.jwt.AccessToken;
import com.tsd.core.jwt.JwtUtils;
import com.tsd.core.shiro.IShiroConst;
import com.tsd.core.shiro.config.ShiroAuthToken;
import com.tsd.core.utils.HlpUtils;
import com.tsd.core.utils.HttpUtil;
import com.tsd.core.utils.RequestUtils;
import com.tsd.core.vo.ErrorCode;
import com.tsd.core.vo.HttpResult;
import com.tsd.core.datasource.SpringContextUtil;
import com.tsd.jwt.JwtHelper;
import com.tsd.jwt.JwtRefreshUtil;
import com.tsd.system.entity.SysUserExt;
import com.tsd.system.utils.SysUtil;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @ClassName: ShiroAuthFilter
 * @Description: 自定义AuthFilter过滤器
 * @Author: Hillpool
 * @Date: 2022/7/15 9:50
 * @Version: 1.0
 */
public class ShiroAuthFilter extends AuthenticatingFilter {


    /**
     * 生成token
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = WebUtils.toHttp(servletRequest);
        String token = (String) request.getAttribute(Constants.REQ_HEADER_NAME_TOKEN);
        String refreshToken = (String) request.getAttribute(Constants.REQ_HEADER_NAME_REFRESH_TOKEN);
        String account_set_sid = (String) request.getAttribute(Constants.HEADER_NAME_ACCOUNT_SET_SID);
        ShiroAuthToken authToken = new ShiroAuthToken(null, null, token, IShiroConst.REALM_NAME_TOKEN, null);
        authToken.setRefresh_token(refreshToken);
        authToken.setAccount_set_sid(account_set_sid);
        return authToken;
    }

    /**
     * 所有请求全部拒绝访问 --
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return WebUtils.toHttp(request).getMethod().equals(RequestMethod.OPTIONS.name());
    }

    /**
     * 拒绝访问的请求，会调用onAccessDenied方法，onAccessDenied方法先获取 token
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    @SuppressWarnings("unchecked")
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = WebUtils.toHttp(servletRequest);
        HttpServletResponse response = WebUtils.toHttp(servletResponse);
        Map<String, Object> params = (Map<String, Object>) request.getAttribute(Constants.REQ_ATTRIBUTE_NAME_PARAMS);
        if (params == null) {
            params = RequestUtils.getParams(request);
        }
        String application = request.getHeader(Constants.REQ_HEADER_NAME_APPLICATION);
        if (!HlpUtils.isEmpty(application)) {
            params.put(Constants.PARAMS_KEY_NAME_GROUP_APPLICATION, application);
        }
        String token = request.getHeader(Constants.REQ_HEADER_NAME_TOKEN);
        if (HlpUtils.isEmpty(token)) {
            token = HlpUtils.getString(params, Constants.REQ_HEADER_NAME_TOKEN);
        }
        String refreshToken = request.getHeader(Constants.REQ_HEADER_NAME_REFRESH_TOKEN);
        if (HlpUtils.isEmpty(refreshToken)) {
            refreshToken = HlpUtils.getString(params, Constants.REQ_HEADER_NAME_REFRESH_TOKEN);
        }
        if (HlpUtils.isEmpty(token) && HlpUtils.isEmpty(refreshToken)) {
            RequestUtils.writeResponse(response, HttpResult.fail(ErrorCode.CODE_NO_LOGINNED, "request的head、form或者json中缺少token参数"));
            return false;
        }
        String account_set_sid = HttpUtil.getAccountSet(request);
        DataSourceContextHolder.setDataSourceType(account_set_sid);
        Claims claims = null;
        if (!HlpUtils.isEmpty(token)) {
            claims = JwtRefreshUtil.getToken(token);
        }
        if (claims == null && !HlpUtils.isEmpty(refreshToken)) {
            Claims refreshClaims = JwtRefreshUtil.getToken(refreshToken);
            if (refreshClaims != null) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "token,refresh_token");
                SysUserExt userExt = SysUtil.getSysUserFromClaims(refreshClaims);
                JwtRefreshUtil.createSysUserToken(userExt);
                response.setHeader(Constants.REQ_HEADER_NAME_TOKEN, userExt.getAccessToken().getToken());
                response.setHeader(Constants.REQ_HEADER_NAME_REFRESH_TOKEN, userExt.getRefreshToken().getToken());
                claims = refreshClaims;
            }
        }
        if (claims == null) {
            RequestUtils.writeResponse(response, HttpResult.fail(ErrorCode.CODE_NO_LOGINNED, "令牌不合法或者已过期，请重新登录"));
            return false;
        }
        SysUserExt userExt = SysUtil.getSysUserFromClaims(claims);
        CacheOperatorApi<AccessToken> tokenCacheOperatorApi = (CacheOperatorApi<AccessToken>) SpringContextUtil.getBean("tokenCacheOperatorApi");
        String cacheKey = SysUtil.getTokenCacheKey(account_set_sid, userExt);
        AccessToken accessToken = tokenCacheOperatorApi.get(cacheKey);
        if (accessToken == null) {
            accessToken = new AccessToken();
            accessToken.setToken(token);
            accessToken.setExpires_in(JwtUtils.getTokenExpiredMs());
            tokenCacheOperatorApi.put(cacheKey, accessToken, JwtUtils.getTokenExpiredSeconds().longValue());
        }
        if (!accessToken.getToken().equalsIgnoreCase(token)) {
            RequestUtils.writeResponse(response, HttpResult.fail(ErrorCode.CODE_NO_LOGINNED, "当前账号已在其他地方登录"));
            return false;
        }
        claims.put(Constants.HEADER_NAME_ACCOUNT_SET_SID, account_set_sid);
        request.setAttribute(Constants.REQ_ATTRIBUTE_NAME_CLAIMS, claims);
        request.setAttribute(Constants.REQ_ATTRIBUTE_NAME_PARAMS, params);
        request.setAttribute(Constants.REQ_HEADER_NAME_TOKEN, token);
        request.setAttribute(Constants.REQ_HEADER_NAME_REFRESH_TOKEN, refreshToken);
        request.setAttribute(Constants.HEADER_NAME_ACCOUNT_SET_SID, account_set_sid);
        Subject subject = super.getSubject(servletRequest, servletResponse);
        if (subject.isAuthenticated()) {
            return true;
        }
        return executeLogin(servletRequest, servletResponse);
    }

    /**
     * token失效时候调用
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        try {
            RequestUtils.writeResponse(httpResponse, HttpResult.fail(ErrorCode.CODE_NO_LOGINNED, "令牌不合法或者已过期，请重新登录"));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return false;
    }
}
